package com.example.bbs.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.bbs.domain.entity.Post;
import com.example.bbs.domain.entity.User;
import com.example.bbs.domain.model.PostDTO;
import com.example.bbs.domain.model.PostVO;
import com.example.bbs.kafka.producer.PostProducer;
import com.example.bbs.mapper.PostMapper;
import com.example.bbs.service.PostService;
import com.example.bbs.service.UserService;
import com.example.bbs.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class PostServiceImpl implements PostService {

    @Resource
    private PostMapper postMapper;
    @Resource
    private RedisTemplate commonRedisTemplate;
    @Resource
    private RedisTemplate postInfoRedisTemplate;

    @Autowired
    private UserService userService;

    @Autowired
    private LikeServiceImpl likeServiceImpl;

    @Autowired
    private PostProducer postProducer;

    private final static String HOT_RANK_INIT_TIME = "2022-01-01 00:00:00";


    /**
     * 初始化操作
     */
    @PostConstruct
    private void init() {
        //系统启动时，加载DB中最近一个月的帖子信息到redis中
        LambdaQueryWrapper<Post> queryWrapper = Wrappers.<Post>lambdaQuery()
                .eq(Post::getCreateTime, TimeUtil.getLastMonthDate(new Date(), 1))
                .eq(Post::getStatus, ConstantUtil.POST_STATUS_ACTIVE);

        List<Post> postList = postMapper.selectList(queryWrapper);
        if (!CollectionUtils.isEmpty(postList)) {
            postList.forEach(e -> {
                //加载帖子详情数据到redis中
                postInfoRedisTemplate.opsForHash().putAll(e.getPostId(), Post.parseEntry(e));
            });
        }
    }

    @Override
    public Post getPostByPostId(String postId) {
        //从缓存中获取
        Post post = getPostByPostSnFromCache(postId);
        if (Objects.isNull(post)) {
            post = postMapper.selectOne(Wrappers.<Post>lambdaQuery().eq(Post::getPostId, postId));

            //异构到缓存中
            Post finalPost = post;
            ThreadPoolUtil.executorService.execute(() -> {
                postInfoRedisTemplate.opsForHash().putAll(postId, Post.parseEntry(finalPost));
            });

        }
        return post;
    }

    /**
     * 获取帖子DTO（帖子数据+作者数据）
     *
     * @param postId
     * @return
     */
    public PostDTO getPostDTO(String postId) {
        Post post = getPostByPostId(postId);
        User user = userService.getUserByUserId(post.getUserId());

        boolean likeStatus;
        User loginUser = HostHold.get();
        if (Objects.isNull(loginUser)) {
            likeStatus = false;
        } else {
            likeStatus = likeServiceImpl.isLikePost(postId, loginUser.getUserId());
        }


        return PostDTO.builder()
                .post(post)
                .user(user)
                .likeStatus(likeStatus)
                .build();
    }


    /**
     * 从缓存中获取全量帖子数据
     *
     * @return
     */
    private List<PostDTO> getAllPostInfoFromCache() {
        //从redis中取出所有post数据
        Set<String> keys = postInfoRedisTemplate.keys("*");
        if (CollectionUtils.isEmpty(keys)) {
            return Collections.EMPTY_LIST;
        }

        //转换为postDTO对象
        List<PostDTO> postDTOList = keys.stream().map(e -> getPostDTO(e)).collect(Collectors.toList());

        return postDTOList;
    }

    /**
     * 从缓存中获取Post
     *
     * @param postId
     * @return
     */
    private Post getPostByPostSnFromCache(String postId) {
        Map postEntries = postInfoRedisTemplate.opsForHash().entries(postId);
        if (CollectionUtils.isEmpty(postEntries)) {
            return null;
        }
        return Post.parseMap(postEntries);
    }


    /**
     * 获取最新帖子列表（最近一个月）
     *
     * @param page
     * @param limit
     * @return
     */
    public PostVO getTheLatestPostInfoList(int page, int limit) {
        //拿到最近一个月的帖子信息
        List<PostDTO> latelyMonthPostDTOList = getAllPostInfoFromCache();
        if (CollectionUtils.isEmpty(latelyMonthPostDTOList)) {
            return PostVO.builder()
                    .totalSize(0)
                    .postDTOList(Collections.EMPTY_LIST)
                    .build();
        }

        //按时间倒序排序
        latelyMonthPostDTOList.sort((a, b) -> (int) (b.getPost().getCreateTime().getTime()
                - a.getPost().getCreateTime().getTime()));
        //置顶贴排在最前面
        latelyMonthPostDTOList.sort((a, b) -> {
            int typeA = a.getPost().getType() == 1 ? 0 : a.getPost().getType();
            int typeB = b.getPost().getType() == 1 ? 0 : b.getPost().getType();
            return typeB - typeA;
        });
        //分页
        int totalSize = latelyMonthPostDTOList.size();

        int start = (page - 1) * limit;
        int end = start + limit;
        if (end > totalSize) {
            end = totalSize;
        }
        List<PostDTO> theLatestPostDTOList = latelyMonthPostDTOList.subList(start, end);

        return PostVO.builder()
                .postDTOList(theLatestPostDTOList)
                .totalSize(totalSize)
                .build();
    }


    /**
     * 获取最热帖子列表（最近一个月）
     *
     * @param page
     * @param limit
     * @return
     */
    public PostVO getTheHottestPostInfoList(int page, int limit) {
        //拿到最近一个月的帖子信息
        List<PostDTO> latelyMonthPostDTOList = getAllPostInfoFromCache();
        if (CollectionUtils.isEmpty(latelyMonthPostDTOList)) {
            return PostVO.builder()
                    .totalSize(0)
                    .postDTOList(Collections.EMPTY_LIST).build();
        }
        //按热度分数倒序排序
        latelyMonthPostDTOList.sort((a, b) -> b.getPost().getScore() - a.getPost().getScore() > 0 ?
                1 : ((b.getPost().getScore().equals(a.getPost().getScore())) ? 0 : -1));

        //置顶贴排在最前面
        latelyMonthPostDTOList.sort((a, b) -> {
            int typeA = a.getPost().getType() == 1 ? 0 : a.getPost().getType();
            int typeB = b.getPost().getType() == 1 ? 0 : b.getPost().getType();
            return typeB - typeA;
        });

        //分页
        int totalSize = latelyMonthPostDTOList.size();

        int start = (page - 1) * limit;
        int end = start + limit;
        if (end > totalSize) {
            end = totalSize;
        }
        List<PostDTO> theHottestPostDTOList = latelyMonthPostDTOList.subList(start, end);

        return PostVO.builder()
                .postDTOList(theHottestPostDTOList)
                .totalSize(totalSize)
                .build();
    }


    /**
     * 保存帖子
     *
     * @param post
     * @return
     */
    public void saveCache(Post post) {
        if (Objects.isNull(post)) {
            return;
        }
        post.setPostId(UniqueIdUtil.generateUniqueId(1));
        post.setUserId(HostHold.get().getUserId());
        post.setLikeCount(0);
        post.setViewCount(0);
        post.setCommentCount(0);
        post.setScore(0D);
        post.setCreateTime(new Date());
        post.setUpdateTime(new Date());
        post.setStatus(ConstantUtil.POST_STATUS_ACTIVE);
        post.setType(ConstantUtil.POST_TYPE_NORMAL);

        //保存到redis
        postInfoRedisTemplate.opsForHash().putAll(post.getPostId(), Post.parseEntry(post));

        //发送新增帖子消息
        postProducer.send("post-insert", post);

        //添加新帖子到热帖集合中, 用于计算热贴排行
        addChangedPostSet(post.getPostId());

    }

    public void save(Post post) {
        postMapper.insert(post);
    }


    /**
     * 统计帖子浏览量
     */
    public void incrPostViewCount(String postId, HttpServletRequest request) {
        String ip = request.getRemoteHost();
        String postLookSetKey = RedisKeyUtil.getPostLookSetKey(postId);
        Boolean member = commonRedisTemplate.opsForSet().isMember(postLookSetKey, ip);
        if (!member) {

            postInfoRedisTemplate.opsForHash().increment(postId, "viewCount", 1);

            commonRedisTemplate.opsForSet().add(postLookSetKey, ip);
            //帖子浏览量增加, 添加到热帖集合中, 用于计算热贴排行
            addChangedPostSet(postId);
        }
        return;
    }


    /**
     * 获取帖子日排行榜
     */
    public PostVO getDayHotPostRank() {
        Set<ZSetOperations.TypedTuple> set = commonRedisTemplate.opsForZSet()
                .reverseRangeWithScores(RedisKeyUtil.HOT_POST_RANK_DAY_KEY, 0, 10);

        List<PostDTO> postDTOList = set.stream().map(e -> getPostDTO((String) e.getValue()))
                .collect(Collectors.toList());

        return PostVO.builder()
                .postDTOList(postDTOList)
                .totalSize(postDTOList.size())
                .build();
    }

    /**
     * 获取帖子周排行榜
     */
    public PostVO getWeekHotPostRank() {

        Set<ZSetOperations.TypedTuple> set = commonRedisTemplate.opsForZSet()
                .reverseRangeWithScores(RedisKeyUtil.HOT_POST_RANK_WEEK_KEY, 0, 10);


        List<PostDTO> postDTOList = set.stream().map(e -> getPostDTO((String) e.getValue()))
                .collect(Collectors.toList());

        return PostVO.builder()
                .postDTOList(postDTOList)
                .totalSize(postDTOList.size())
                .build();
    }


    /**
     * 获取帖子小时排行榜
     */
    public List<Post> getHourHotPostRank() {
        long currentTimeKey = System.currentTimeMillis() / (1000 * 60 * 60);
        String hotPostListHourKey = RedisKeyUtil.getHotPostListHourKey(currentTimeKey);
        Set<ZSetOperations.TypedTuple> set = commonRedisTemplate.opsForZSet().reverseRangeWithScores(hotPostListHourKey, 0, 8);
        List<Post> posts = set.stream().map(e -> getPostByPostId((String) e.getValue())).collect(Collectors.toList());

        return posts;
    }

    //实时收集有变化的帖子添加到redis中
    public void addChangedPostSet(String postId) {
        commonRedisTemplate.opsForSet().add(RedisKeyUtil.CHANGED_POST_SET_KEY, postId);
    }



    public void update(Post post) {
        postMapper.update(post, Wrappers.<Post>lambdaUpdate().eq(Post::getPostId, post.getPostId()));
    }

    public void updateCache(Post post) {
        postInfoRedisTemplate.opsForHash().putAll(post.getPostId(), Post.parseEntry(post));
        //发送帖子更新消息
        postProducer.send("post-update", post);
    }

    public void deleteCache(String postId) {
        postInfoRedisTemplate.opsForHash().put(postId, "status", ConstantUtil.POST_STATUS_INACTIVE);
        //发送帖子更新消息
        Post post = getPostByPostSnFromCache(postId);
        postProducer.send("post-update", post);
    }

    @Override
    public void setWonderful(String postId) {
        postInfoRedisTemplate.opsForHash().put(postId, "type", ConstantUtil.POST_TYPE_WONDERFUL);
        //发送帖子更新消息
        Post post = getPostByPostSnFromCache(postId);
        postProducer.send("post-update", post);

        //添加新帖子到热帖集合中, 用于计算热贴排行
        addChangedPostSet(postId);

    }

    @Override
    public void setTop(String postId) {
        postInfoRedisTemplate.opsForHash().put(postId, "type", ConstantUtil.POST_TYPE_TOP);
        //发送帖子更新消息
        Post post = getPostByPostSnFromCache(postId);
        postProducer.send("post-update", post);

        //添加新帖子到热帖集合中, 用于计算热贴排行
        addChangedPostSet(postId);
    }


}
